﻿using Microsoft.VisualStudio.GraphModel;
using Timtopia;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.VisualStudio.GraphModel.Schemas;

namespace Timtopia.DGMLPosterView
{
    public class ModularDecomposition
    {
        Dictionary<string, string> qIDToSimpleID = new Dictionary<string, string>();
        Dictionary<string, string> simpleIDToQID = new Dictionary<string, string>();
        Graph graph;
        HashSet<GraphNode> orphans;

        static ModularDecomposition()
        {
            int numModuleLevels = 3;
            moduleLevels = new GraphCategory[numModuleLevels];
            Schema = new GraphSchema("ModularDecomposition");
            for (int i = 0; i < numModuleLevels; ++i)
            {
                moduleLevels[i] = Schema.Categories.AddNewCategory("ModuleLevel" + i);
            }
            shortestPathLinkCategory = Schema.Categories.AddNewCategory("ShortestPath");
            startNodeCategory = Schema.Categories.AddNewCategory("StartNode");
            endNodeCategory = Schema.Categories.AddNewCategory("EndNode");
        }

        public static GraphSchema Schema;

        public static void Apply(Graph graph)
        {
            new ModularDecomposition(graph);
        }

        private ModularDecomposition(Graph graph)
        {
            this.graph = graph;
            using (var scope = new GraphTransactionScope())
            {
                graph.DocumentSchema.AddSchema(Schema);
                var orphansList = graph.OrphanNodes.ToList();
                orphans = new HashSet<GraphNode>(orphansList);
                int n = orphans.Count;
                for (int i = 0; i < n; ++i)
                {
                    var v = orphansList[i];
                    string simpleId;
                    simpleId = GetID(n, i);
                    string qid = v.Id.ToString();
                    qIDToSimpleID.Add(qid, simpleId);
                    simpleIDToQID.Add(simpleId, qid);
                }
                string gstring = "";
                foreach (var l in graph.Links.Where(l => IsTopLevel(l)))
                {
                    string sourceId = qIDToSimpleID[l.Source.Id.ToString()];
                    string targetId = qIDToSimpleID[l.Target.Id.ToString()];
                    gstring += sourceId + "-" + targetId + ",";
                }

                //using (FileStream fs = new FileStream(@"c:\Temp\inputgraph.dgml", FileMode.Create))
                //{
                //    byte[] x = new UTF8Encoding(true).GetBytes(gstring);
                //    fs.Write(x, 0, x.Length);
                //}

                ProcessStartInfo perlStartInfo = new ProcessStartInfo("ModularDecomposition.exe");
                perlStartInfo.UseShellExecute = false;
                perlStartInfo.CreateNoWindow = true;
                perlStartInfo.RedirectStandardInput = true;
                perlStartInfo.RedirectStandardOutput = true;
                //perlStartInfo.RedirectStandardError = true;

                Process perl = new Process();
                perl.StartInfo = perlStartInfo;
                perl.Start();
                perl.StandardInput.WriteLine(gstring);
                perl.WaitForExit();

                string output = perl.StandardOutput.ReadToEnd();
                Node root = Parser.ParseNode(output);
                //Parser.PrintTree(root, 0);

                RootModularize(root);

                scope.Complete();
            }
        }

        public static string GetID(int n, int i)
        {
            string simpleId;

            // currently the modular decomposition only works properly with single char ids, 
            // so use upper or lower case alpha if we can.
            if (n <= 52)
            {
                int unicodeOffset = i <= 26 ? 65 : 97;
                simpleId = "" + Convert.ToChar(i + unicodeOffset);
            }
            else
            {
                // this works a bit
                simpleId = "node" + i;
            }
            return simpleId;
        }

        private bool IsTopLevel(GraphLink l)
        {
            return orphans.Contains(l.Source) && orphans.Contains(l.Target);
        }

        private static bool IsVisible(GraphLink l)
        {
            return !l.IsContainment && 
                IsVisible(l.Source) && IsVisible(l.Target);
        }

        private static bool IsVisible(GraphNode v)
        {
            return !v.ParentGroups.Any() || v.ParentGroups.Any(g => g.IsExpanded);
        }
        
        private void RootModularize(Node v)
        {
            foreach (var c in v.Children)
            {
                var childNode = Modularize(c, 0);
            }
        }

        static GraphCategory[] moduleLevels;

        public static GraphCategory[] ModuleLevelCategories
        {
            get { return moduleLevels; }
        }

        static GraphCategory shortestPathLinkCategory;
        public static GraphCategory ShortestPathLinkCategory { get { return shortestPathLinkCategory; } }
        static GraphCategory startNodeCategory;
        public static GraphCategory StartNodeCategory { get { return startNodeCategory; } }
        static GraphCategory endNodeCategory;
        public static GraphCategory EndNodeCategory { get { return endNodeCategory; } }

        private bool IsCompleteSetOfLeaves(Node node) 
        {
            foreach (var c in node.Children)
            {
                string qid;
                GraphNode u;
                if (!simpleIDToQID.TryGetValue(c.Label, out qid))
                {
                    return false;
                }
                u = graph.Nodes.GetOrCreate(qid);
                var outNeighbors = new HashSet<string>(u.OutgoingLinks.Select(l => l.Target.Id.ToString()));
                var inNeighbors = new HashSet<string>(u.IncomingLinks.Select(l => l.Source.Id.ToString()));
                foreach (var d in node.Children)
                {
                    if (c == d) continue;
                    if (!simpleIDToQID.TryGetValue(d.Label, out qid)
                        || !outNeighbors.Contains(qid)
                        || !inNeighbors.Contains(qid))
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        private bool IsDisjointSetOfLeaves(Node node)
        {
            foreach (var c in node.Children)
            {
                string qid;
                GraphNode u;
                if (!simpleIDToQID.TryGetValue(c.Label, out qid))
                {
                    return false;
                }
                u = graph.Nodes.GetOrCreate(qid);
                var outNeighbors = new HashSet<string>(u.OutgoingLinks.Select(l => l.Target.Id.ToString()));
                var inNeighbors = new HashSet<string>(u.IncomingLinks.Select(l => l.Source.Id.ToString()));
                foreach (var d in node.Children)
                {
                    if (c == d) continue;
                    if (!simpleIDToQID.TryGetValue(d.Label, out qid)
                        || outNeighbors.Contains(qid)
                        || inNeighbors.Contains(qid))
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        private GraphNode Modularize(Node v, int depth)
        {
            GraphNode node;
            string qid;
            if (!simpleIDToQID.TryGetValue(v.Label, out qid))
            {
                node = graph.Nodes.GetOrCreate(v.Label, v.Label, moduleLevels[depth%3]);
                node.IsGroup = true;
                simpleIDToQID.Add(node.Id.ToString(), node.Id.ToString());
            }
            else
            {
                node = graph.Nodes.Get(qid);
            }
            var childLabels = new List<string>();
            var children = new List<GraphNode>();
            foreach (var c in v.Children)
            {
                var childNode = Modularize(c, depth + 1);
                children.Add(childNode);
                var clink = graph.Links.GetOrCreate(node, childNode);
                clink.AddCategory(GraphCommonSchema.Contains);
                childLabels.Add(childNode.Label);
            }
            if (childLabels.Any())
            {
                node.Label = Parser.CommonPrefix(childLabels);
                foreach (var c in children)
                {
                    c.Label = c.Label.Substring(node.Label.Length);
                }
            }
            return node;
        }
    }
}
